// 7.1.2 定义改进的Sales_data类
/**
 * 成员函数的声明必须在类的内部，它的定义则既可以在类的内部也可以在类的外部。
 * 作为接口组成部分的非成员函数，例如add、read和print等，它们的定义和声明都在类的外部。
 *
 * 定义在类内部的函数是隐式的inline函数（参见6.5.2节，第214页）。
 * 
 * 成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。
 * 当我们调用一个成员函数时，用请求该函数的对象地址初始化this。
 * 
 * 任何对类成员的直接访问都被看作this的隐式引用
 * 也就是说，当isbn使用bookNo时，它隐式地使用this指向的成员，就像我们书写了this->bookNo一样
 * 
 * 因为this的目的总是指向“这个”对象，所以this是一个常量指针（参见2.4.2节，第56页），我们不允许改变this中保存的地址。
 * 
 * 常量对象，以及常量对象的引用或指针都只能调用常量成员函数。
 * 
 * 编译器分两步处理类：首先编译成员的声明，然后才轮到成员函数体（如果有的话）。
 * 因此，成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
 */

#include <iostream>
using std::cerr, std::clog;
using std::cin, std::cout, std::endl;

struct Sales_data
{
    // 新成员，关于Sales_data对象的操作
    // 定义在类内部的函数是隐式的inline函数
    std::string isbn() const { return bookNo; } // const成员函数
    //std::string isbn() const { return this->bookNo; } // 同上，但没有必要，因为this是隐式的
    /**
     * 可把isbn常量成员函数想象成如下形式（将this定义为指向常量的常量指针）：
     * std::string Sales_data::isbn(const Sales_data *const this) { return this->bookNo; }
     * 
     * 常量对象，以及常量对象的引用或指针都只能调用常量成员函数。
     */
    Sales_data &combine(const Sales_data &);
    double avg_price() const;

    // 数据成员和2.6.1节相比没有改变
    std::string bookNo;
    unsigned units_sold = 0; // unsigned是unsigned int的缩写
    double revenue = 0.0;
}; // 类必须以分号;结尾

// Sales_data的非成员接口函数
Sales_data add(const Sales_data &, const Sales_data &);
std::ostream &print(std::ostream &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);

// 类外部定义的成员的名字必须包含它所属的类名
double Sales_data::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
}

// 定义一个返回this对象的函数
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold; // 把rhs的成员加到this对象的成员上
    revenue += rhs.revenue;
    return *this; // 返回调用该函数的对象，记住this是个常量指针，不要直接返回this，要返回this指向的对象
}

int main()
{
    Sales_data total;     // 保存当前求和结果的变量
    if (read(cin, total)) // 读入第一笔交易
    {
        Sales_data trans;        // 保存下一条交易数据的变量
        while (read(cin, trans)) // 读入剩余的交易数据
        {
            if (total.isbn() == trans.isbn()) // 检查isbn
            {
                total.combine(trans); // 更新变量total当前的值
            }
            else
            {
                print(cout, total) << endl; // 输出结果
                total = trans;              // 处理下一本书
            }
        }
        print(cout, total) << endl; // 输出最后一条交易
    }
    else
    {
        cerr << "No data?!" << endl; // 没有输入任何信息，通知用户
    }

    return 0;
}